<?php

namespace Pinenut\Model2\Adapter;

use Pinenut\Model2\Finder;
use Pinenut\Model2\Updater;
use Pinenut\Log;

class Mongo extends \Pinenut\Model2\Adapter {
	static $config = '\MongoAdapterConfig';
	
	/**
	 *
	 * @return \MongoId
	 */
	static function generateId() {
		return new \MongoId ();
	}
	
	/**
	 * Соединения с разными коллекциями
	 *
	 * @var \MongoCollection
	 */
	protected $connect = null;
	
	/**
	 * Коннект к коллекции базы
	 *
	 * @return \MongoCollection
	 */
	function connect() {
		if (null === $this->connect) {
			$configClassName = static::$config;
			$config = $configClassName::get ();
			$client = new \MongoClient ( @$config ['server'], @$config ['options'] );
			$db = $client->selectDB ( @$config ['db'] );
			$this->connect = $db->selectCollection ( $this->collection );
		}
		return $this->connect;
	}
	protected function doCriteria(array $where) {
		$query = array ();
		
		foreach ( $where as $row ) {
			
			// // типекастим
			// if ($type = static::getFieldType($row['field'])) {
			// if (is_array($row['value'])) {
			// foreach ($row['value'] as &$v) {
			// $v = static::typeCast($v, $type);
			// }
			// } else {
			// $row['value'] = static::typeCast($row['value'], $type);
			// }
			// }
			
			// не красиво, но для быстроты - уходим с отсутствием типизации идентификатора
			if ('_id' == $row ['field']) {
				if (is_array ( $row ['value'] )) {
					foreach ( $row ['value'] as &$value ) {
						$value = new \MongoId ( ( string ) $value );
					}
				} else {
					$row ['value'] = new \MongoId ( ( string ) $row ['value'] );
				}
			}

			
			// формируем запрос в зависимости от операции
			
			switch ($row ['op']) {
				case Finder::OP_EQ :
					$query [$row ['field']] = $row ['value'];
					break;
				case Finder::OP_NEQ :
					$query [$row ['field']] ['$ne'] = $row ['value'];
					break;
				case Finder::OP_IN :
					$query [$row ['field']] ['$in'] = $row ['value'];
					break;
				case Finder::OP_ALL :
					$query [$row ['field']] ['$all'] = $row ['value'];
					break;
				case Finder::OP_NIN :
					$query [$row ['field']] ['$nin'] = $row ['value'];
					break;
				
				case Finder::OP_GT :
					$query [$row ['field']] ['$gt'] = $row ['value'];
					break;
				
				case Finder::OP_LT :
					$query [$row ['field']] ['$lt'] = $row ['value'];
					break;
				
				case Finder::OP_GTE :
					$query [$row ['field']] ['$gte'] = $row ['value'];
					break;
				
				case Finder::OP_LTE :
					$query [$row ['field']] ['$lte'] = $row ['value'];
					break;
			}
		}
//		print_r($query); exit;
		return $query;
	}
	
	/**
	 * Собираем и выполняем запрос на выборку данных
	 *
	 * @param array $where        	
	 * @param array $fields        	
	 * @return array
	 */
	function select(array $where = null, array $fields = null, array $order = null, $offset = null, $limit = null) {
		
		// Log::begin();
		$query = $this->doCriteria ( $where );
		
		
		$result = $this->connect ()->find ( $query, $fields );
		if (null !== $order)
			$result->sort ( $order );
		if (null !== $offset)
			$result->skip ( intval ( $offset ) );
		if (null !== $limit)
			$result->limit ( intval ( $limit ) );
					 
		$data = iterator_to_array ( $result );
		// Log::end('Select', array('query'=>$query, 'fields'=>$fields, 'explain'=>$explain));
		return $data;
	}
	
	/**
	 * Возвращаем кол-во записей по условию
	 *
	 * @param array $where        	
	 * @return int
	 */
	function count(array $where = null) {
		$query = $this->doCriteria ( $where );
		return $this->connect ()->count ( $query );
	}
	
	/**
	 * Возвращаем кол-во записей по условию
	 *
	 * @param array $where        	
	 * @return int
	 */
	function remove(array $where = null) {
		$query = $this->doCriteria ( $where );
		return $this->connect ()->remove ( $query );
	}
	
	/**
	 * Обновляем запись
	 *
	 * @param array $where        	
	 * @param array $operators        	
	 */
	function update(array $where = null, array $operators = null, $upsert = false) {
		Log::begin ();
		
		// подготавливаем условие
		$query = $this->doCriteria ( $where );
		$removeFromSet = $addToSet = $inc = $set = array ();
		
		// готовим операции
		
		if (isset ( $operators [Updater::OP_REMOVEFROMSET] ) && is_array ( $operators [Updater::OP_REMOVEFROMSET] )) {
			$removeFromSet = $operators [Updater::OP_REMOVEFROMSET];
		}
		if (isset ( $operators [Updater::OP_ADDTOSET] ) && is_array ( $operators [Updater::OP_ADDTOSET] )) {
			$addToSet = $operators [Updater::OP_ADDTOSET];
		}
		
		if (isset ( $operators [Updater::OP_INC] ) && is_array ( $operators [Updater::OP_INC] ))
			foreach ( $operators [Updater::OP_INC] as $field => $data ) {
				$inc [$field] = $data;
			}
		
		if (isset ( $operators [Updater::OP_SET] ) && is_array ( $operators [Updater::OP_SET] ))
			foreach ( $operators [Updater::OP_SET] as $field => $data ) {
				$set [$field] = $data;
			}
		
		$op = array ();
		$options = array ();
		
		$options ['upsert'] = $upsert;
		
		if (! empty ( $removeFromSet ))
			$op ['$pullAll'] = $removeFromSet;
		
		if (! empty ( $addToSet ))
			$op ['$addToSet'] = $addToSet; 	 	
		
		if (! empty ( $inc ))
			$op ['$inc'] = $inc;
		
		if (! empty ( $set ))
			$op ['$set'] = $set;
		
		Log::end ( 'Update-prepare', array (
				'query' => $query,
				'op' => $op 
		) );
		// echo "Update:";
		// print_r($query);
		// print_r($op);
		Log::begin ();
		try {
			$res = $this->connect ()->update ( $query, $op, $options );
		} catch ( Exception $e ) {
			Log::end ( 'Update-ERROR', array (
					'query' => $query,
					'op' => $op,
					'error' => $e 
			) );
			throw $e;
		}
		
		Log::end ( 'Update-execute', array (
				'query' => $query,
				'op' => $op 
		) );
	}
	
	/**
	 * Добавляем новую запись
	 *
	 * @param array $where        	
	 * @param array $operators        	
	 */
	function insert(array $set = null) {
		// Log::begin();
		// готовим массив данных из сета
		$data = static::set2array ( $set );
		$res = $this->connect ()->insert ( $data );
		// echo "Insert:";
		// print_r ( $data );
		// Log::end('Insert', array('data'=>$data,));
	}
	static function set2array($set) {
		$data = [ ];
		foreach ( $set as $path => $value ) {
			$parts = explode ( '.', $path );
			foreach ( $parts as &$part ) {
				if (intval ( $part ) > 0) {
					$part = intval ( $part ) - 1; // FIXME: поправка, блин, вносится при создании массива в связях например... не красиво
				}
			}
			switch (count ( $parts )) {
				case 1 :
					$data [$parts [0]] = $value;
					break;
				case 2 :
					$data [$parts [0]] [$parts [1]] = $value;
					break;
				case 3 :
					$data [$parts [0]] [$parts [1]] [$parts [2]] = $value;
					break;
				case 4 :
					$data [$parts [0]] [$parts [1]] [$parts [2]] [$parts [3]] = $value;
					break;
				case 5 :
					$data [$parts [0]] [$parts [1]] [$parts [2]] [$parts [3]] [$parts [4]] = $value;
					break;
			}
		}
		return $data;
	}
}